/**
 * 
 */
package javapop.framework.generic.tool;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;

import javapop.framework.ParseInput;
import javapop.framework.generic.GChoice;
import javapop.framework.generic.GDefRegle;
import javapop.framework.generic.GExprBracket;
import javapop.framework.generic.GExprOperand;
import javapop.framework.generic.GExprOperator;
import javapop.framework.generic.GExprParser;
import javapop.framework.generic.GIgnore;
import javapop.framework.generic.GKeyword;
import javapop.framework.generic.GKeywords;
import javapop.framework.generic.GListOf;
import javapop.framework.generic.GLiteral;
import javapop.framework.generic.GMaybe;
import javapop.framework.generic.GMaybeOrDefault;
import javapop.framework.generic.GMeta;
import javapop.framework.generic.GParser;
import javapop.framework.generic.GRefParser;
import javapop.framework.generic.GRegexp;
import javapop.framework.generic.GRegexpWithoutKeywords;
import javapop.framework.generic.GRegle;
import javapop.framework.generic.GTransform;
import javapop.framework.generic.GTuple;
import javapop.framework.generic.GrammarTree;

/**
 * @author Hakim Belhaouari
 *
 */
public class SampleGenerator {

	private SampleGeneratorVisitor v;
	private SampleGeneratorAcceptedWordProvider provider;
	private List<String> keywords;

	class Token {
		String token = "";
		ArrayList<Token> next = new ArrayList<SampleGenerator.Token>();

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(token);
			sb.append(" ");
			sb.append(next.toString());
			return sb.toString();
		}

		protected ArrayList<StringBuilder> generate(StringBuilder sb) throws IOException {
			ArrayList<StringBuilder> res = new ArrayList<StringBuilder>();
			if(token != null && !token.trim().isEmpty()) {
				sb.append(token);
				sb.append(" ");
			}
			if(next.size() == 0)
				res.add(sb);
			else {
				for (Token t : next) {
					StringBuilder s = new StringBuilder(sb);
					res.addAll(t.generate(s));
				}
			}
			return res;
		}
		
		protected void addAtEnd(Token t) {
			if(next.size() == 0)
				next.add(t);
			else {
				Stack<Token> todo = new Stack<SampleGenerator.Token>();
				todo.push(this);
				while(!todo.isEmpty()) {
					Token cur = todo.pop();
					if(cur.next.isEmpty())
						cur.next.add(t);
					else {
						for (Token token : cur.next) {
							if(!todo.contains(token))
								todo.push(token);
						}
					}
				}
			}
		}

	}

	private HashMap<String, GParser> refs;
	private Token result;

	public SampleGenerator(List<GrammarTree> grammar, String name) throws GrammarVisitorException {
		this(grammar,name,new SampleGeneratorAcceptedWordProvider("ident","javapop.example","javapop.example.MyObject","\"a string generated by JavaPop\""));
	}
	
	
	public SampleGenerator(List<GrammarTree> grammar, String name,SampleGeneratorAcceptedWordProvider provider) throws GrammarVisitorException {
		refs = new HashMap<String, GParser>();
		v = new SampleGeneratorVisitor();
		keywords = new LinkedList<String>();
		this.provider = provider; //
		for (GrammarTree g : grammar) {
			if(g instanceof GRegle) {
				GRegle r = (GRegle)g;
				refs.put(r.getRegle().getName(), r.getParser());
			}
			if(g instanceof GKeywords) {
				for (GKeyword key : ((GKeywords) g).getKeywords()) {
					keywords.add(key.getKeyword());
				}
			}
			if(g instanceof GKeyword) {
				keywords.add(((GKeyword) g).getKeyword());
			}
		}
		for (GrammarTree g : grammar) {
			if(g instanceof GRegle) {
				if(name.equals(((GRegle) g).getRegle().getName())) {
					GParser p = ((GRegle) g).getParser();
					result = p.visit(v);
				}
			}
		}
	}

	

	public Token getToken() {
		return result;
	}

	class SampleGeneratorVisitor implements GrammarVisitor<Token, String, GrammarVisitorException> {

		
		@Override
		public void init(List<GrammarTree> grammar)
				throws GrammarVisitorException {

		}

		@Override
		public String treatment(ParseInput input)
				throws GrammarVisitorException {
			return null;
		}

		@Override
		public Token visit(GChoice o) throws GrammarVisitorException {
			Token t = new Token();
			for (GParser p : o.getChoices()) {
				Token r = p.visit(this);
				t.next.add(r);
			}
			return t;
		}

		@Override
		public Token visit(GDefRegle r) throws GrammarVisitorException {
			// TODO Auto-generated method stub
			return null;
		}

		@Override
		public Token visit(GExprBracket b) throws GrammarVisitorException {
			// TODO Auto-generated method stub
			return null;
		}

		@Override
		public Token visit(GExprOperand o) throws GrammarVisitorException {
			// TODO Auto-generated method stub
			return null;
		}

		@Override
		public Token visit(GExprOperator o) throws GrammarVisitorException {
			// TODO Auto-generated method stub
			return null;
		}

		@Override
		public Token visit(GExprParser o) throws GrammarVisitorException {
			// TODO Auto-generated method stub
			return null;
		}

		@Override
		public Token visit(GIgnore o) throws GrammarVisitorException {
			return o.getParser().visit(this);
		}

		@Override
		public Token visit(GKeyword o) throws GrammarVisitorException {
			// TODO Auto-generated method stub
			return null;
		}

		@Override
		public Token visit(GListOf o) throws GrammarVisitorException {
			Token t = new Token();
			// Zero
			t.next.add(new Token());

			// Un element
			Token p = o.getParser().visit(this);
			t.next.add(p);

			// Plusieurs (ici 2)
			Token deux = o.getParser().visit(this);
			t.next.add(deux);
			deux = o.getParser().visit(this);
			Token vir = new Token();
			vir.token = o.getSep();
			vir.next.add(o.getParser().visit(this));

			return t;
		}

		@Override
		public Token visit(GLiteral o) throws GrammarVisitorException {
			Token t= new Token();
			t.token =  o.getLiteral();
			return t;
		}

		@Override
		public Token visit(GMaybeOrDefault o) throws GrammarVisitorException {
			Token t= new Token();
			Token b1 = new Token();
			b1.next.add(o.getParser().visit(this));
			Token b2 = new Token();
			t.next.add(b1);
			t.next.add(b2);
			return t;
		}

		@Override
		public Token visit(GMeta o) throws GrammarVisitorException {
			// TODO Auto-generated method stub
			return null;
		}

		@Override
		public Token visit(GRefParser o) throws GrammarVisitorException {
			if("skip".equals(o.getRef()))
				return new Token();

			if(refs.containsKey(o.getRef())) {
				//if(!visited.contains(o.getRef())) {
				//visited.add(o.getRef());
				return refs.get(o.getRef()).visit(this);
				/*}
				else {
					Token t = new Token();
					t.token = "<|"+o.getRef()+"|>";
					return t;
				}*/
			}
			else {
				Token t = new Token();
				t.token = "<"+o.getRef()+">";
				return t;
			}
		}

		@Override
		public Token visit(GRegexp o) throws GrammarVisitorException {
			Token t = new Token();
			
			t.token = provider.searchWord(o.getRegexp());
			return t;
		}

		@Override
		public Token visit(GRegexpWithoutKeywords o)
				throws GrammarVisitorException {
			Token t = new Token();
			t.token = provider.searchWord(o.getRegexp(),keywords);
			return t;
		}

		@Override
		public Token visit(GRegle o) throws GrammarVisitorException {
			// TODO Auto-generated method stub
			return null;
		}

		@Override
		public Token visit(GTransform o) throws GrammarVisitorException {
			return o.getParser().visit(this);
		}

		@Override
		public Token visit(GTuple o) throws GrammarVisitorException {
			Token t = new Token();
			Token u = t;
			for (GParser p : o.getList()) {
				Token f = p.visit(this);
				t.addAtEnd(f);
				t = f;
			}
			return u;
		}

		@Override
		public Token visit(GKeywords o) throws GrammarVisitorException {
			
			return null;
		}

		@Override
		public Token visit(GMaybe o) throws GrammarVisitorException {
			Token t = new Token();
			Token b1 = o.getParser().visit(this);
			Token b2 = new Token();
			t.next.add(b1);
			t.next.add(b2);
			return t;
		}

	}


	@Override
	public String toString() {
		return result.toString();
	}

	private String outputdir = ".";
	private int count = 1;

	private String genFileName() {
		return outputdir+File.separator+"testFile"+(count++)+".pop";
	}

	public void generate(String outputdir) throws IOException {
		this.outputdir = outputdir;
		ArrayList<StringBuilder> files = result.generate(new StringBuilder());
		for (StringBuilder stringBuilder : files) {
			FileOutputStream fos = new FileOutputStream(genFileName());
			fos.write(stringBuilder.toString().getBytes());
			fos.flush();
			fos.close();
		}
	}
}
